﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using GIMS.Def;
using GIMS.Def.Arguments;
using GIMS.Def.Flags;
using GIMS.Def.Operators;
using GIMS.GUI.Options;
using GraphSharp.Controls;
using GIMS.GUI.GraphItems;
using GIMS.GUI.GeneralConfig;
using GIMS.Def.Settings;

namespace GIMS.GUI
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly MainWindowViewModel viewModel;
        private readonly List<VertexControl> verticesWithEventHandler;
        private readonly List<EdgeControl> edgesWithEventHandler;
        private CustomVertex currentVertex;

        public MainWindow()
        {
            viewModel = new MainWindowViewModel();
            DataContext = viewModel;
            verticesWithEventHandler = new List<VertexControl>();
            edgesWithEventHandler = new List<EdgeControl>();
            InitializeComponent();
            graphLayout.LayoutUpdated += new EventHandler(GraphLayout_LayoutUpdated);
            Focus();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                AddEventsToGraphElements();
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void GraphLayout_LayoutUpdated(object sender, EventArgs e)
        {
            try
            {
                AddEventsToGraphElements();
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void Vertex_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            try
            {
                var vc = sender as VertexControl;
                var customVertex = vc.DataContext as CustomVertex;
                if (null != customVertex)
                {
                    ConfigureOperation(customVertex);
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void EdgeSequenzeUp_MouseUp(object sender, MouseButtonEventArgs e)
        {
            try
            {
                var edge = (((FrameworkElement)sender)).DataContext as CustomEdge;
                if (null != edge)
                {
                    viewModel.IncrementEdgeSequence(edge);
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void EdgeSequenzeDown_MouseUp(object sender, MouseButtonEventArgs e)
        {
            try
            {
                var edge = (((FrameworkElement)sender)).DataContext as CustomEdge;
                if (null != edge)
                {
                    viewModel.DecrementEdgeSequence(edge);
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void CreateSkriptCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var dialog = new Microsoft.Win32.SaveFileDialog();
            dialog.DefaultExt = ".cmd";
            dialog.Filter = "Command Line file (*.cmd)|*.cmd";
            if (dialog.ShowDialog() == true)
            {
                string filename = dialog.FileName;
                string skript = String.Concat(viewModel.CurrentProgram, " ", viewModel.CreateScript());
                var tw = new StreamWriter(filename);
                tw.Write(skript);
                tw.Close();
            }
        }

        private void CreateSkriptCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = viewModel.IsIMPathCorrect
                && viewModel.IsScriptExecutable()
                && viewModel.GetScriptStatus() != ScripStatusEnum.IncorrectParam;
        }

        private void Execute_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = viewModel.IsIMPathCorrect
                && viewModel.IsScriptExecutable() 
                && viewModel.GetScriptStatus() == ScripStatusEnum.NoParam;
        }

        private void ConfigureOperatorCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var customVertex = (CustomVertex)e.Parameter;
            ConfigureOperation(customVertex);
        }

        private void DeleteOperatorCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var customVertex = (CustomVertex)e.Parameter;
            viewModel.Graph.DeleteVertex(customVertex);
        }

        private void DeleteOperatorCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            var customVertex = (CustomVertex)e.Parameter;
            e.CanExecute = viewModel.Graph.CanDeleteVertex(customVertex);
        }

        private void DeleteEdgeCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var customEdge = (CustomEdge)e.Parameter;
            viewModel.Graph.DeleteEdge(customEdge);
        }

        private void DeleteEdgeCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;//todo: wann erlaubt?
        }

        private void StartNewEdgeCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            currentVertex = (CustomVertex)e.Parameter;
        }

        private void StartNewEdgeCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;//todo: wann erlaubt?
        }

        private void EndNewEdgeCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            NewEdgeOperation((CustomVertex)e.Parameter);
        }

        private void EndNewEdgeCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = IsNewEdgeAllowed((CustomVertex)e.Parameter);
        }

        private void SetPreviewVertexCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            viewModel.CurrentVertexForPreview = (CustomVertex)e.Parameter;
        }

        private void OptionsWindow_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var optionsWindowViewModel = new OptionsWindowViewModel();
            var optionsWindow = new OptionsWindow(optionsWindowViewModel);
            optionsWindow.ShowDialog();
        }

        private void Execute_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            viewModel.ExecuteGraphScript();
        }

        private void SetPreviewVertexCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true; //todo: wann erlaubt?
        }

        private void ShowGlobalSettingsCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var generalConfigWindowViewModel = new GeneralConfigWindowViewModel(GlobalSettings.Instance);
            var generalConfigWindow = new GeneralConfigWindow(generalConfigWindowViewModel);
            generalConfigWindow.ShowDialog();
        }

        private void NewConvertCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (CheckAndClear())
            {
                viewModel.CurrentProgram = Program.convert;
            }
        }

        private void NewConvertCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = viewModel.IsIMPathCorrect;
        }

        private void NewMogrifyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (CheckAndClear())
            {
                viewModel.CurrentProgram = Program.mogrify;
            }
        }

        private void NewMogrifyCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = viewModel.IsIMPathCorrect;
        }

        private void SetAsParamCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var vertex = (CustomVertex)e.Parameter;
            var pathArgument = (PathArgument)vertex.IMOperator.Argument;
            pathArgument.IsParameter = true;
        }

        private void UnsetAsParamCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var vertex = (CustomVertex)e.Parameter;
            var pathArgument = (PathArgument)vertex.IMOperator.Argument;
            pathArgument.IsParameter = false;
        }

        private void UnsetAsParamCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            var vertex = (CustomVertex)e.Parameter;
            var pathArgument = vertex.IMOperator.Argument as PathArgument;
            e.CanExecute = pathArgument != null && pathArgument.IsParameter;
        }

        private void UserGuideCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            string path = Path.Combine(Directory.GetCurrentDirectory(), @"Images\User_guide.pdf");
            System.Diagnostics.Process.Start(path);
        }


        #region Drag and Drop

        private void Operation_MouseMove(object sender, MouseEventArgs e)
        {
            try
            {
                Image image = sender as Image;
                if (image != null
                    && Mouse.LeftButton == MouseButtonState.Pressed)
                {
                    DragDrop.DoDragDrop(image,
                                         image.Tag,
                                         DragDropEffects.Copy);
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        #region DragOver

        private void vertexTopDrop_DragOver(object sender, DragEventArgs e)
        {
            var targetVertex = ((FrameworkElement)(sender)).DataContext as CustomVertex;
            string operatorName = (string)e.Data.GetData(DataFormats.Text);

            if (IsDropAllowed(operatorName, Position.First, targetVertex))
            {
                e.Effects = DragDropEffects.Copy;
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
        }

        private void vertexBottomDrop_DragOver(object sender, DragEventArgs e)
        {
            try
            {
                var targetVertex = ((FrameworkElement)(sender)).DataContext as CustomVertex;
                string operatorName = (string)e.Data.GetData(DataFormats.Text);

                if (IsDropAllowed(operatorName, Position.Last, targetVertex))
                {
                    e.Effects = DragDropEffects.Copy;
                }
                else
                {
                    e.Effects = DragDropEffects.None;
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void edge_DragOver(object sender, DragEventArgs e)
        {
            try
            {
                var targetEdge = ((System.Windows.FrameworkElement)(sender)).DataContext as CustomEdge;
                string operatorName = (string)e.Data.GetData(DataFormats.Text);

                if (IsDropAllowed(operatorName, Position.Between, targetEdge.Target, targetEdge.Source))
                {
                    e.Effects = DragDropEffects.Copy;
                }
                else
                {
                    e.Effects = DragDropEffects.None;
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private void graph_DragOver(object sender, DragEventArgs e)
        {
            try
            {
                string operatorName = (string)e.Data.GetData(DataFormats.Text);

                if (!viewModel.Graph.IsGraphEmpty)
                    return;

                if (IsDropAllowed(operatorName))
                {
                    e.Effects = DragDropEffects.Copy;
                }
                else
                {
                    e.Effects = DragDropEffects.None;
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        #endregion DragOver

        #region Drop

        private void graph_Drop(object sender, DragEventArgs e)
        {
            if (!viewModel.Graph.IsGraphEmpty)
                return;

            string operatorName = (string)e.Data.GetData(DataFormats.Text);
            viewModel.Graph.AddNewVertex(new CustomVertex(OperatorFactory.GetOperator(operatorName)));
        }

        private void vertexTopDrop_Drop(object sender, DragEventArgs e)
        {
            var targetVertex = ((System.Windows.FrameworkElement)(sender)).DataContext as CustomVertex;
            string operatorName = (string)e.Data.GetData(DataFormats.Text);
            AddNewVertex(operatorName, Position.First, targetVertex);
        }

        private void vertexBottomDrop_Drop(object sender, DragEventArgs e)
        {
            var targetVertex = ((System.Windows.FrameworkElement)(sender)).DataContext as CustomVertex;
            string operatorName = (string)e.Data.GetData(DataFormats.Text);
            AddNewVertex(operatorName, Position.Last, targetVertex);
        }

        private void edge_Drop(object sender, DragEventArgs e)
        {
            var edge = sender as GraphSharp.Controls.EdgeControl;
            if (null != edge)
            {
                var customEdge = edge.Edge as CustomEdge;
                if (null != customEdge)
                {
                    string operatorName = (string)e.Data.GetData(DataFormats.Text);
                    AddNewVertex(operatorName, Position.Between, (CustomVertex)customEdge.Target, (CustomVertex)customEdge.Source);
                }
            }
        }

        #endregion Drop

        #endregion Drag and Drop

        #region Private Helper Methods

        private bool CheckAndClear()
        {
            bool checkOk = false;

            if (!viewModel.Graph.IsGraphEmpty)
            {
                var result = MessageBox.Show("All existing operators will be removed! Would you like to proceed?", "Proceed?",
                                             MessageBoxButton.YesNo);
                if (result == MessageBoxResult.Yes)
                {
                    viewModel.Graph.Clear();
                    return checkOk = true;
                }
            }
            else
            {
                checkOk = true;
            }
            return checkOk;
        }

        private void AddEventsToGraphElements()
        {
            foreach (var child in graphLayout.Children)
            {
                if (child is VertexControl)
                {
                    var vertex = child as VertexControl;
                    if (!verticesWithEventHandler.Contains(vertex))
                    {
                        verticesWithEventHandler.Add(vertex);
                        //vertex.AddHandler(MouseLeftButtonUpEvent,
                        //    new MouseButtonEventHandler(Vertex_MouseLeftUp), true);
                        //vertex.AddHandler(MouseUpEvent,
                        //    new MouseButtonEventHandler(Vertex_MouseLeftUp), true);
                        vertex.AddHandler(MouseDoubleClickEvent,
                            new MouseButtonEventHandler(Vertex_MouseDoubleClick));
                    }
                }
                else if (child is EdgeControl)
                {
                    var edge = child as EdgeControl;
                    if (!edgesWithEventHandler.Contains(edge))
                    {
                        edgesWithEventHandler.Add(edge);
                        var buttonUp = FindChild<Button>(edge, "EdgeSequenzeUp");
                        var buttonDown = FindChild<Button>(edge, "EdgeSequenzeDown");
                        if (null != buttonUp)
                        {
                            buttonUp.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(EdgeSequenzeUp_MouseUp), true);
                        }
                        if (null != buttonDown)
                        {
                            buttonDown.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(EdgeSequenzeDown_MouseUp), true);
                        }
                    }
                }
            }
        }

        private void HandleException(Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }

        private void NewEdgeOperation(CustomVertex vertex)
        {
            var sourceCustomVertex = currentVertex;
            var targetCustomVertex = vertex;
            if (!viewModel.Graph.AddNewEdgeBetween(sourceCustomVertex, targetCustomVertex))
            {
                MessageBox.Show("It's not possible to add this Edge!");
            }
            currentVertex = null;
        }

        private void AddNewVertex(string operatorName, Position position, CustomVertex targetVertex, CustomVertex sourceVertex = null)
        {
            operatorName = operatorName.ToLower();
            Operator imOperator = OperatorFactory.GetOperator(operatorName);
            CustomVertex newVertex = new CustomVertex(imOperator);
            switch (position)
            {
                case Position.First:
                    viewModel.Graph.AddNewSourceToVertex(targetVertex, newVertex);
                    break;
                case Position.Last:
                    viewModel.Graph.AddNewVertexToSource(targetVertex, newVertex);
                    break;
                case Position.Between:
                    if (null == sourceVertex)
                    {
                        throw new ArgumentNullException("sourceVertex");
                    }
                    if (sourceVertex == targetVertex)
                    {
                        throw new ArgumentException("sourceVertex and targetVertex must not be the same");
                    }
                    viewModel.Graph.AddNewVertexBetween(sourceVertex, targetVertex, newVertex);
                    break;
            }
        }

        private bool IsNewEdgeAllowed(CustomVertex targetVertex)
        {
            var isAllowed = true;

            if (currentVertex == null)
            {
                isAllowed = false;
            }
            else if (!targetVertex.IMOperator.ExpectsMultipleInputs)
            {
                isAllowed = viewModel.Graph.GetParentsCount(targetVertex) == 0;
            }

            return isAllowed;
        }

        private bool IsDropAllowed(string operatorName, Position position, CustomVertex targetVertex, CustomVertex sourceVertex = null)
        {
            var isAllowed = true;
            operatorName = operatorName.ToLower();

            var imOperator = OperatorFactory.GetOperator(operatorName);
            switch (position)
            {
                case Position.First:
                    if (imOperator.Position == Position.Last
                        || targetVertex.IMOperator.Position == Position.First)
                    {
                        isAllowed = false;
                    }
                    else if (targetVertex.IMOperator is SaveOperator
                        && viewModel.Graph.GetParentsCount(targetVertex) > 0)
                    {
                        isAllowed = false;
                    }
                    else if (!targetVertex.IMOperator.ExpectsMultipleInputs)
                    {
                        isAllowed = viewModel.Graph.GetParentsCount(targetVertex) == 0;
                    }
                    break;
                case Position.Last:
                    if (imOperator.Position == Position.First
                        || targetVertex.IMOperator.Position == Position.Last)
                    {
                        isAllowed = false;
                    }
                    else if (imOperator is SaveOperator
                        && viewModel.Graph.HasSave)
                    {
                        isAllowed = false;
                    }
                    break;
                case Position.Between:
                    if (imOperator.Position != Position.Between
                        || sourceVertex == null
                        || sourceVertex.IMOperator.Position == Position.Last
                        || targetVertex.IMOperator.Position == Position.First)
                    {
                        isAllowed = false;
                    }
                    break;
            }

            return isAllowed;
        }

        private bool IsDropAllowed(string operatorName)
        {
            var imOperator = OperatorFactory.GetOperator(operatorName);
            return viewModel.Graph.VertexCount == 0
                        && imOperator.Position == Position.First;
        }

        private void ConfigureOperation(CustomVertex customVertex)
        {
            var generalConfigWindowViewModel = new GeneralConfigWindowViewModel(customVertex.IMOperator);
            var generalConfigWindow = new GeneralConfigWindow(generalConfigWindowViewModel);
            generalConfigWindow.ShowDialog();
        }


        /// <summary>
        /// Finds a Child of a given item in the visual tree. 
        /// </summary>
        /// <param name="parent">A direct parent of the queried item.</param>
        /// <typeparam name="T">The type of the queried item.</typeparam>
        /// <param name="childName">x:Name or Name of child. </param>
        /// <returns>The first parent item that matches the submitted type parameter. 
        /// If not matching item can be found, 
        /// a null parent is being returned.</returns>
        public static T FindChild<T>(DependencyObject parent, string childName)
           where T : DependencyObject
        {
            // Confirm parent and childName are valid. 
            if (parent == null) return null;

            T foundChild = null;

            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);
                // If the child is not of the request child type child
                T childType = child as T;
                if (childType == null)
                {
                    // recursively drill down the tree
                    foundChild = FindChild<T>(child, childName);

                    // If the child is found, break so we do not overwrite the found child. 
                    if (foundChild != null) break;
                }
                else if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;
                    // If the child's name is set for search
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child's name is of the request name
                        foundChild = (T)child;
                        break;
                    }
                }
                else
                {
                    // child element found.
                    foundChild = (T)child;
                    break;
                }
            }

            return foundChild;
        }

        #endregion Private Helper Methods

    }
}
